package coro

import "runtime"

type inner struct{ ch chan any }
type Ctx struct{ inner }
type Coro[T any] struct{ *inner }

type CoroAny interface {
	Resume(any) (any, bool)
	Close()
	Closed() bool
}

type finish[T any] struct{ v T }

func newInner() *inner {
	return &inner{make(chan any)}
}

func (coro Coro[T]) Closed() bool {
	return coro.ch == nil
}

func (coro Coro[T]) Close() {
	runtime.SetFinalizer(coro.inner, nil)
	close(coro.ch)
	coro.ch = nil
}

func Create[T any](f func(Ctx) T) Coro[T] {
	coro := Coro[T]{newInner()}
	runtime.SetFinalizer(coro.inner, func(c *inner) {
		Coro[T]{c}.Close()
	})
	go func(ctx Ctx) {
		ctx.receive()
		r := f(ctx)
		ctx.ch <- finish[T]{r}
	}(Ctx{*coro.inner})
	return coro
}

func (coro Coro[T]) Resume(v any) (any, bool) {
	coro.ch <- v
	r := <-coro.ch
	switch r := r.(type) {
	case finish[T]:
		coro.Close()
		return r.v, true
	default:
		return r, false
	}
}

func (ctx Ctx) receive() any {
	v, ok := <-ctx.ch
	if !ok {
		runtime.Goexit()
	}
	return v
}

func (ctx Ctx) Yield(v any) any {
	ctx.ch <- v
	return ctx.receive()
}
